{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Using Labels and Defining Domains\n",
    "\n",
    "Labels are a way to 'bookmark' certain pores for easier lookup later, such as specifying boundary conditions.  When networks are generated they include a set of relevant labels that have been added for convenience.  It's also possible for users to add their own labels.  This tutorial will cover how they work, how to add them, and how to use them."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "import openpnm as op\n",
    "import numpy as np\n",
    "op.visualization.set_mpl_style()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Using Predefined Labels\n",
    "\n",
    "The simple ``Cubic`` network has several labels added during creating.  We can see these labels when we inspecting (i.e. ``print``) the network,"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "══════════════════════════════════════════════════════════════════════════════\n",
      "net : <openpnm.network.Cubic at 0x20846d21e50>\n",
      "――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――\n",
      "  #  Properties                                                   Valid Values\n",
      "――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――\n",
      "  2  pore.coords                                                       25 / 25\n",
      "  3  throat.conns                                                      40 / 40\n",
      "――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――\n",
      "  #  Labels                                                 Assigned Locations\n",
      "――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――\n",
      "  2  pore.surface                                                           16\n",
      "  3  throat.surface                                                         16\n",
      "  4  pore.left                                                               5\n",
      "  5  pore.right                                                              5\n",
      "  6  pore.front                                                              5\n",
      "  7  pore.back                                                               5\n",
      "――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――\n"
     ]
    }
   ],
   "source": [
    "pn = op.network.Cubic(shape=[5, 5, 1])\n",
    "print(pn)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "In the above output we can see 2 'properties', which is the term used for numerical data, and 10 'labels'.  Labels are also Numpy arrays, so are stored in the OpenPNM objects along 'property' arrays. The difference is that 'labels' are ``boolean`` type (i.e. ``True``, ``False``).\n",
    "\n",
    "A ``boolean`` array, such as *'pore.left'* is an *Np* long with ``True`` values indicating which pores possess that label.  \n",
    "\n",
    "The label 'pore.left' clearly indicates which pores on of the 'left' side of the network.  We can inspect the label array directly:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[ True  True  True  True  True False False False False False False False\n",
      " False False False False False False False False False False False False\n",
      " False]\n"
     ]
    }
   ],
   "source": [
    "print(pn['pore.left'])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "But it's more useful to extract the *locations* of the true values:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {
    "tags": []
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([0, 1, 2, 3, 4], dtype=int64)"
      ]
     },
     "execution_count": 5,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "np.where(pn['pore.left'])[0]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "One of the advantages of using boolean masks as labels is that it's easy to use boolean logic to find pores and throats with a certain combination of labels.  For instance, to find all pores that are labelled both `'left'` and `'top'` we just multiply these label arrays together:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[0]\n"
     ]
    }
   ],
   "source": [
    "corner_pores = pn['pore.left']*pn['pore.front']\n",
    "print(np.where(corner_pores)[0])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## The `pores` and `throats` methods\n",
    "\n",
    "OpenPNM objects all have method for accessing pore and throat locations by label without calling ``where`` or building your own boolean logic filters. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {
    "tags": []
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([0, 1, 2, 3, 4])"
      ]
     },
     "execution_count": 9,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "pn.pores('left')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```{note} **Points to Note about Using Labels**\n",
    "\n",
    "  * Labels can be applied to throats in exactly the same way as described here for pores.\n",
    "  * Every OpenPNM object has a ``pores`` and a ``throats`` method that can be used to retrieve the pore or throat indices  where a label or combination of labels has been applied.\n",
    "  * The format of creating an entire ND-array to label just a few locations may seem a bit wasteful of data storage since many ``False`` values are created to indicate pores do *not* have a label. Worse, Boolean arrays are actually stored as 8-bit integer arrays by Numpy, so they consume 8x more memory than needed.  Nonetheless, the investment is worth it since Boolean arrays are so convenient.  They can be used to index into Numpy arrays (e.g. ``arr[mask]``) and great for combining logic (e.g. ``locs = mask1 * mask2``).\n",
    "  * Labels are used throughout any given script, particularly for specifying boundary conditions, so are an important feature to understand.\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Using the ``mode`` argument in the ``pores`` and ``throats`` methods\n",
    "\n",
    "The advantage of the using the OpenPNM method is that you can create more complex queries, such as pores that are 'left' *or* 'back':"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {
    "tags": []
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([ 0,  1,  2,  3,  4,  9, 14, 19, 24])"
      ]
     },
     "execution_count": 7,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "pn.pores(['left', 'back'])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The default mode is `'or'` which means \"retrieve all pores that are labelled 'left' or 'back', or both\".  This list can be reduced to pores that are *both* left *and* back, by specifying ``mode='and'``:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {
    "tags": []
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([4])"
      ]
     },
     "execution_count": 9,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "pn.pores(['left', 'back'], mode='and')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Because this is a 2D network, we only found one 'corner' pore.  In 3D this would have found all the pores on the 'edge'. \n",
    "\n",
    "The `mode` argument using standard set-theory terminology, such as ``'or'``, ``'nor'``, etc. This can make it a bit confusing, but each mode's operation is well described in the docstring. We'll go over these below:"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "`mode='nor'` means \"retrieve all the pores indices that do not possess the 'left' or 'back' label.  `'not'` is also accepted:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([ 5,  6,  7,  8, 10, 11, 12, 13, 15, 16, 17, 18, 20, 21, 22, 23])"
      ]
     },
     "execution_count": 13,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "pn.pores(['left', 'back'], mode='nor')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "`mode='xor'` means \"retrieve all the pore indices that have 'left' of 'back' but not both\""
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([ 0,  1,  2,  3,  9, 14, 19, 24])"
      ]
     },
     "execution_count": 15,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "pn.pores(['left', 'back'], mode='xor')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "`mode='nand'` means \"retrieve pore indices that have all but one of the given labels\":"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([ 0,  1,  2,  3,  4, 20, 21, 22, 23, 24])"
      ]
     },
     "execution_count": 21,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "pn.pores(['left', 'right'], mode='nand')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Lastly, `'xnor'` means \"retrieve pore indices that have more than one of the given label\":"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([4])"
      ]
     },
     "execution_count": 23,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "pn.pores(['left', 'back'], mode='xnor')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Defining Custom Labels\n",
    "\n",
    "It is common in OpenPNM scripts to do some complex searching to find all pores that satisfy some set of conditions.  It can be helpful to label these for later use.  There are two ways to do this, by directly creating a boolean mask and storing it, or by calling the ``set_label`` method."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Creating Boolean Masks\n",
    "Assume we want to label the 'corner' pore we found above.  First catch the result in a variable:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "Ps = pn.pores(['left', 'back'], mode='and')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Then create an empty label array (filled with ``False`` values) on the network:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[False False False False False False False False False False False False\n",
      " False False False False False False False False False False False False\n",
      " False]\n"
     ]
    }
   ],
   "source": [
    "pn['pore.corner'] = False\n",
    "print(pn['pore.corner'])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Now insert ``True`` values at the desired array indices corresponding to the pore location:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[4]\n"
     ]
    }
   ],
   "source": [
    "pn['pore.corner'][Ps] = True\n",
    "print(pn.pores('corner'))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Using ``set_label``\n",
    "The process of creating an empty label array, then filling the ``True`` values is a bit annoying, so there is a helper/shortcut method on all OpenPNM objects.  It can be used to create a new label, or to edit an existing one.  Let's label another corner pore as 'pore.corner':"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[0 4]\n"
     ]
    }
   ],
   "source": [
    "Ps = Ps = pn.pores(['left', 'front'], mode='and')\n",
    "pn.set_label(label='corner', pores=Ps)\n",
    "print(pn.pores('corner'))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The ``set_label`` method has a ``mode`` argument that can be used to change the behavior, such as ``'add'`` or ``'remove'`` labels from the given locations (default is ``mode='add'``), ``'purge'`` all existing indices from the given label, or ``'remove'`` which purges then adds the given pores to the specified label.  "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "False\n"
     ]
    }
   ],
   "source": [
    "pn.set_label(label='pore.corner', mode='purge')\n",
    "print('pore.corner' in pn.keys())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Using the @ syntax to read and write data\n",
    "The @ syntax provides a shortcut for reading and writing data to certain locations, defined by labels. The @ syntax is also used to define \"domains\" where pore network properties differ, but  first let's look at using @:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([[0.5, 0.5, 0.5],\n",
       "       [0.5, 1.5, 0.5],\n",
       "       [0.5, 2.5, 0.5],\n",
       "       [0.5, 3.5, 0.5],\n",
       "       [0.5, 4.5, 0.5]])"
      ]
     },
     "execution_count": 13,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "pn['pore.coords@left']"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "This certainly saves some typing!  It's also pretty intuitive since ``@`` is usually read as \"at\", inferring a location. \n",
    "\n",
    "The @ syntax can also be used to write data as well. Let's create an array of 1.0s, then use the @ syntax to change them:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {},
   "outputs": [],
   "source": [
    "pn['pore.values'] = 1.0"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "If we supply a scalar, it is written to all locations belong to ``'left'``:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[2. 2. 2. 2. 2. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1.\n",
      " 1.]\n"
     ]
    }
   ],
   "source": [
    "pn['pore.values@left'] = 2.0\n",
    "print(pn['pore.values'])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We can of course pass in an array, which must have the correct number of elements:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[2. 2. 2. 2. 2. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 4. 5. 6. 7.\n",
      " 8.]\n"
     ]
    }
   ],
   "source": [
    "pn['pore.values@right'] = [4, 5, 6, 7, 8]\n",
    "print(pn['pore.values'])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "One useful bonus is that you can create an array and assign values to certain locations at the same time:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[ 2.  2.  2.  2.  2. nan nan nan nan nan nan nan nan nan nan nan nan nan\n",
      " nan nan nan nan nan nan nan]\n"
     ]
    }
   ],
   "source": [
    "pn['pore.new_array@left'] = 2.0\n",
    "print(pn['pore.new_array'])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The above line created an empty array of ``nans``, then added ``2.0`` to the pores labelled ``'left'``.  This was not previously possible without first creating an empty array before adding ``2.0`` to specific locations."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "You can use any label that is defined, and it will overwrite any values already present if that label happens to overlap the label used previously:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[ 3.  2.  2.  2.  2.  3. nan nan nan nan  3. nan nan nan nan  3. nan nan\n",
      " nan nan  3. nan nan nan nan]\n"
     ]
    }
   ],
   "source": [
    "pn['pore.new_array@front'] = 3.0\n",
    "print(pn['pore.new_array'])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "which overwrote some locations that had ``2.0``, since some pores are both ``'front'`` and ``'left'``, as well as overwrote some of the ``nan`` values."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[3. 2. 2. 2. 2.]\n"
     ]
    }
   ],
   "source": [
    "print(pn['pore.new_array@left'])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Defining Subdomains\n",
    "\n",
    "Using the @ symbol for data read/write as shown above is actually a side effect of a *major conceptual shift* made in V3. The ``Geometry`` and ``Physics`` objects are now *gone*.  There was essentially only one use case for these, which was to model heterogeneous domains, like bimodal pore size distributions or layered structures.  \n",
    "\n",
    "In V2 this was accomplished by using 2 (or more) ``Geometry`` objects to represent each class of pores, with unique pore-scale models attached to each.  Without getting lost in the details, it is sufficient to say that having separate objects for managing each class of pores (and/or throats) created a *lot* of complications, both to the user and to the maintenance of the backend.  \n",
    "\n",
    "In V3 we have developed what we think is a *much tidier approach* to managing heterogeneous domains.  Instead of creating multiple ``Geometry`` objects (and consequently multiple ``Physics`` objects), you now add all the pore-scale models to the ``Network`` and ``Phase`` objects directly.  The trick is that when adding models you specify one additional argument: the  ``domain`` (i.e. pores or throats) to which the model applies, as follows:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "metadata": {},
   "outputs": [],
   "source": [
    "pn.add_model(propname='pore.seed', \n",
    "             model=op.models.geometry.pore_seed.random,\n",
    "             domain='left',\n",
    "             seed=0,\n",
    "             num_range=[0.1, 0.5])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "where ``domain`` is given the label ``'left'`` which has already been defined on the network. \n",
    "\n",
    "This means that to create a heterogeneous model you only need to create labels marking the pores and/or throats of each domain, then pass those labels when adding models. You can also leave ``domain`` unspecified (``None``) which means the model is applied everywhere. For the above case, we can see that the ``'pore.seed'`` model was computed for 4 locations (corresponding the 4 pores labelled ``'left'``):"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "══════════════════════════════════════════════════════════════════════════════\n",
      "net : <openpnm.network.Cubic at 0x1f0626a3a40>\n",
      "――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――\n",
      "  #  Properties                                                   Valid Values\n",
      "――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――\n",
      "  1  pore.coords                                                       25 / 25\n",
      "  2  pore.new_array                                                     9 / 25\n",
      "  3  pore.seed                                                          5 / 25\n",
      "  4  pore.values                                                       25 / 25\n",
      "  5  throat.conns                                                      40 / 40\n",
      "――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――\n",
      "  #  Labels                                                 Assigned Locations\n",
      "――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――\n",
      "  1  pore.back                                                               5\n",
      "  2  pore.front                                                              5\n",
      "  3  pore.left                                                               5\n",
      "  4  pore.right                                                              5\n",
      "  5  pore.surface                                                           16\n",
      "  6  throat.surface                                                         16\n",
      "――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――\n"
     ]
    }
   ],
   "source": [
    "print(pn)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The power of this new approach is really visible when we consider applying a model with different parameters to a different set of pores:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "metadata": {},
   "outputs": [],
   "source": [
    "pn.add_model(propname='pore.seed', \n",
    "             model=op.models.geometry.pore_seed.random,\n",
    "             domain='right',\n",
    "             seed=0,\n",
    "             num_range=[0.5, 0.9])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Now the ``pore.seed'`` values exist on 10 locations."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 26,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "══════════════════════════════════════════════════════════════════════════════\n",
      "net : <openpnm.network.Cubic at 0x1f0626a3a40>\n",
      "――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――\n",
      "  #  Properties                                                   Valid Values\n",
      "――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――\n",
      "  1  pore.coords                                                       25 / 25\n",
      "  2  pore.new_array                                                     9 / 25\n",
      "  3  pore.seed                                                         10 / 25\n",
      "  4  pore.values                                                       25 / 25\n",
      "  5  throat.conns                                                      40 / 40\n",
      "――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――\n",
      "  #  Labels                                                 Assigned Locations\n",
      "――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――\n",
      "  1  pore.back                                                               5\n",
      "  2  pore.front                                                              5\n",
      "  3  pore.left                                                               5\n",
      "  4  pore.right                                                              5\n",
      "  5  pore.surface                                                           16\n",
      "  6  throat.surface                                                         16\n",
      "――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――\n"
     ]
    }
   ],
   "source": [
    "print(pn)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The new approach was made possible by changing how pore-scale models are stored on objects.  Each object has a ``models`` attribute, which is a ``dict`` where the ``key`` corresponds to the property being calculated.  So the values stored in ``pn['pore.seed']`` are computed by the model stored as ``pn.models['pore.seed']``.  The new ``@`` notation makes it possible to store multiple models for ``'pore.seed'`` that apply to different location on the same object.  This can be seen below by printing the models attribute:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 27,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――\n",
      "#   Property Name                       Parameter                 Value\n",
      "―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――\n",
      "1   pore.coordination_number@all        model:                    coordination_number\n",
      "                                        regeneration mode:        deferred\n",
      "―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――\n",
      "2   throat.spacing@all                  model:                    pore_to_pore_distance\n",
      "                                        regeneration mode:        deferred\n",
      "―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――\n",
      "3   pore.seed@left                      model:                    random\n",
      "                                        seed:                     0\n",
      "                                        num_range:                [0.1, 0.5]\n",
      "                                        regeneration mode:        normal\n",
      "―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――\n",
      "4   pore.seed@right                     model:                    random\n",
      "                                        seed:                     0\n",
      "                                        num_range:                [0.5, 0.9]\n",
      "                                        regeneration mode:        normal\n",
      "―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――\n"
     ]
    }
   ],
   "source": [
    "print(pn.models)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Appending ``@`` to the model name creates a unique dictionary key. OpenPNM recognizes that the models in ``'pore.seed@left'`` and ``'pore.seed@right'`` both compute values of ``'pore.seed'``, and directs the outputs of each function to the correct locations, which it can infer from the ``@right/left`` portion of the key."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Defining and Changing Subdomain Locations\n",
    "It becomes trivial to define and redefine the locations of a domain.  This simply requires changing where ``pn['pore.left']`` is ``True``.  This is demonstrated as follows:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 28,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([0, 1, 2, 3, 4])"
      ]
     },
     "execution_count": 28,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "pn.pores('left')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 29,
   "metadata": {},
   "outputs": [],
   "source": [
    "pn['pore.left'][[4, 5]] = True\n",
    "del pn['pore.seed']"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 30,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "══════════════════════════════════════════════════════════════════════════════\n",
      "net : <openpnm.network.Cubic at 0x1f0626a3a40>\n",
      "――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――\n",
      "  #  Properties                                                   Valid Values\n",
      "――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――\n",
      "  1  pore.coords                                                       25 / 25\n",
      "  2  pore.new_array                                                     9 / 25\n",
      "  3  pore.seed                                                          6 / 25\n",
      "  4  pore.values                                                       25 / 25\n",
      "  5  throat.conns                                                      40 / 40\n",
      "――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――\n",
      "  #  Labels                                                 Assigned Locations\n",
      "――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――\n",
      "  1  pore.back                                                               5\n",
      "  2  pore.front                                                              5\n",
      "  3  pore.left                                                               6\n",
      "  4  pore.right                                                              5\n",
      "  5  pore.surface                                                           16\n",
      "  6  throat.surface                                                         16\n",
      "――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――\n"
     ]
    }
   ],
   "source": [
    "pn.run_model('pore.seed@left')\n",
    "print(pn)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "It can now been observed that ``'pore.seed'`` values are found in 6 locations because the domain labelled ``'left'`` was expanded by 2 pores. "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Mixing Full Domain and Subdomain Models\n",
    "When defining two separate subdomains, the pore and throat sizes are often the only thing that is different.  In V2, however, it was recommended practice to include ALL the additional models on each subdomain object as well, such as volume calculations.  With the @ syntax, only models that actually differ between the domains need to be specifically dealt with.\n",
    "\n",
    "This is demonstrated below by first deleting the individual ``'pore.seed'`` models applied above, and replacing them with a single model that applies uniform values on all locations, then applying two different normal distributions to the ``'left'`` and ``'right'`` domains."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 31,
   "metadata": {},
   "outputs": [],
   "source": [
    "del pn.models['pore.seed@left']\n",
    "del pn.models['pore.seed@right']"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 32,
   "metadata": {},
   "outputs": [],
   "source": [
    "pn.add_model(propname='pore.seed', \n",
    "             model=op.models.geometry.pore_seed.random)\n",
    "pn.add_model(propname='pore.diameter', \n",
    "             model=op.models.geometry.pore_size.normal,\n",
    "             domain='left',\n",
    "             scale=0.1, \n",
    "             loc=1,\n",
    "             seeds='pore.seed')\n",
    "pn.add_model(propname='pore.diameter', \n",
    "             model=op.models.geometry.pore_size.normal,\n",
    "             domain='right',\n",
    "             scale=2, \n",
    "             loc=10,\n",
    "             seeds='pore.seed')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "As can be seen in the figures below, the ``'pore.seed'`` values are uniformly distributed on all locations, but ``'pore.diameter'`` differs due to the different parameter used in each model."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 33,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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I444gtNPP5158+Zx8skn89xzz7Fu3ToWLFjA3Llz+dCHPsQLL7wAVKbgO//883nLW97C1772NW666Sbe/OY386Y3vYlly5bx7LPPvuQ9zjjjDD72sY9x7LHH8olPfIIrr7ySs88+G4Af/ehHLFy4kKOOOorzzjuP/fbbb+fznn32WU4++WSOOOII3vve95KZfPazn2XLli0ce+yxHHvssXUdu6RiGKolSY1r3fkwOLBr2+BApb1OfX19LF++nA0bNnDAAQdw0UUXccYZZ/DVr36VjRs3sn37di655JKd+0+bNo3vfe97HH/88Xz605/m5ptv5t5776Wnp4eLLrpo1Pd4+OGHufnmm/nrv/7rXdo/8pGP8JGPfIS7776bGTNm7PJYb28vF198MQ888ACPPvood9xxB+eccw4zZszg1ltv5dZbb6372CVNPEO1JKlxPb25tvYazJo1i0WLFgHwvve9j3Xr1jFnzhwOP/xwAE4//XRuv/32nfufeuqpANx111088MADLFq0iPnz53PVVVfx2GOPjfoey5Yto62t7SXtd955J8uWLQPgPe95zy6PHX300cycOZO99tqL+fPns2nTprqPVVLxXFFRktS4DpxZHfoxSnudIqKm/ffdd18AMpMTTjiBq6++eszPqcUrX/nKnffb2trYvn17za8hafLZUy1JalzHnQftHbu2tXdU2uv0+OOPc+eddwJw9dVXc/zxx7Np0yYeeeQRAL785S/ztre97SXPW7hwIXfcccfO/Z577jkefvjhmt574cKFfOMb3wDgmmuuGdNz9t9/f5555pma3kfS5DFUS5Ia17xT4Pc/CwfOAqJy+/ufrbTX6cgjj+Sqq65i3rx5/OxnP+OjH/0oV1xxBcuWLWPu3LnstddenHnmmS953vTp07nyyis57bTTmDdvHgsXLuShhx4C4LzzzuP6669/2fe++OKLueiiizj66KN58sknOfDAA1/2OcuXL+cd73iHFypKDSoys+waatLT05Pr168vuwxJGpeIuCcze8quY7KMds5+8MEHOfLII0uqqGLTpk2cdNJJ3H///aW8/3PPPUdHRwcRwTXXXMPVV1/NddddV9drNsK/q9RsajlnO6ZakqRJds8993D22WeTmXR2dnL55ZeXXZKkOhmqJUktZ/bs2aX1UgP89m//Nj/4wQ9Ke39JE88x1ZIk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",
      "text/plain": [
       "<Figure size 864x432 with 2 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "import matplotlib.pyplot as plt\n",
    "fig, ax = plt.subplots(1, 2, figsize=[12, 6])\n",
    "\n",
    "ax[0].plot(pn.Ps, pn['pore.seed'], 'o')\n",
    "ax[0].set_ylabel('pore seed')\n",
    "ax[0].set_xlabel('pore index')\n",
    "\n",
    "ax[1].plot(pn.pores('left'), pn['pore.diameter@left'], 'o', label='pore.left')\n",
    "ax[1].plot(pn.pores('right'), pn['pore.diameter@right'], 'o', label='pore.right')\n",
    "ax[1].set_ylabel('pore diameter')\n",
    "ax[1].set_xlabel('pore index')\n",
    "ax[1].legend();"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "And now we can apply a model to the full domain that computes the pore volume, using values of pore diameter that were computed uniquely for each domain:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 34,
   "metadata": {},
   "outputs": [],
   "source": [
    "pn.add_model(propname='pore.volume', \n",
    "             model=op.models.geometry.pore_volume.sphere)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 35,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "iVBORw0KGgoAAAANSUhEUgAAAYkAAAFzCAYAAADL1PXCAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjUuMCwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8/fFQqAAAACXBIWXMAAAsTAAALEwEAmpwYAAAbU0lEQVR4nO3df7Bf9V3n8efLkMJt1QnYwEAIhdYYpVobvcV2cDoqtUHXMbEjGtSarTg4imsd1yhxVquOjGyzOjo7UostNa5tMSpCtjpNMa1b26mFG9Ka8iOSBQpJEFJrxure0pC+94/vSXsT7if53nDPvTf3+3zMMOecz/dzznkfvsN9cc7nnPNNVSFJ0nS+Yr4LkCQtXIaEJKnJkJAkNRkSkqQmQ0KS1GRISJKazuprw0lWA382pemlwK8Bf9K1Xwo8BvxQVf1rt85m4DrgKPBzVbXjZPt48YtfXJdeeulsly5Ji9quXbs+U1XLh+mbuXhOIskS4ADwbcANwGer6uYkNwLnVtUvJ7kceC9wBXAR8LfA11XV0dZ2x8fHa2Jiovf6JWkxSbKrqsaH6TtXl5uuAv5vVX0aWAds7dq3Auu7+XXA7VX1TFU9CuxjEBiSpHkyVyGxgcFZAsAFVfUkQDc9v2tfATwxZZ39XdtxklyfZCLJxKFDh3osWZLUe0gkeQHw/cCfn6rrNG3PuRZWVbdW1XhVjS9fPtQlNUnSaZqLM4nvAe6rqqe65aeSXAjQTZ/u2vcDK6esdzFwcA7qkyQ1zEVIXMuXLzUBbAc2dvMbgbumtG9IcnaSy4BVwD1zUJ8kqaG3W2ABkrwQ+G7gp6Y03wxsS3Id8DhwDUBV3Z9kG/AA8Cxww8nubJIk9a/XkKiq/wd8zQlt/8Lgbqfp+t8E3NRnTZKk4fnEtSSpyZCQJDX1erlJkhaCO3cfYMuOvRw8PMlFy8bYtHY169c85zEsTcOQkLSo3bn7AJvv2MPkkcF9MAcOT7L5jj0ABsUQvNwkaVHbsmPvlwLimMkjR9myY+88VXRmMSQkLWoHD0/OqF3HMyQkLWoXLRubUbuOZ0hIWtQ2rV3N2NIlx7WNLV3CprWr56miM4sD15IWtWOD097ddHoMCUmL3vo1KwyF0+TlJklSkyEhSWoyJCRJTYaEJKnJkJAkNRkSkqQmQ0KS1GRISJKaDAlJUpMhIUlqMiQkSU2GhCSpyZCQJDUZEpKkJkNCktRkSEiSmgwJSVKTISFJajIkJElNhoQkqcmQkCQ1GRKSpCZDQpLUZEhIkpoMCUlSU68hkWRZkr9I8lCSB5O8Jsl5Se5O8nA3PXdK/81J9iXZm2Rtn7VJkk6t7zOJ3wfeX1VfD3wz8CBwI7CzqlYBO7tlklwObABeDlwN3JJkSc/1SZJOoreQSPLVwGuBdwJU1Req6jCwDtjaddsKrO/m1wG3V9UzVfUosA+4oq/6JEmn1ueZxEuBQ8C7kuxO8o4kLwIuqKonAbrp+V3/FcATU9bf37UdJ8n1SSaSTBw6dKjH8iVJfYbEWcC3AG+rqjXAf9BdWmrING31nIaqW6tqvKrGly9fPjuVSpKm1WdI7Af2V9XHu+W/YBAaTyW5EKCbPj2l/8op618MHOyxPknSKfQWElX1z8ATSVZ3TVcBDwDbgY1d20bgrm5+O7AhydlJLgNWAff0VZ8k6dTO6nn7/wV4d5IXAI8Ab2IQTNuSXAc8DlwDUFX3J9nGIEieBW6oqqM91ydJOoleQ6KqPgGMT/PRVY3+NwE39VmTJGl4PnEtSWoyJCRJTYaEJKnJkJAkNRkSkqQmQ0KS1GRISJKaDAlJUpMhIUlqMiQkSU2GhCSpyZCQJDUZEpKkJkNCktRkSEiSmgwJSVKTISFJajIkJElNhoQkqcmQkCQ1GRKSpCZDQpLUZEhIkpoMCUlSkyEhSWoyJCRJTYaEJKnJkJAkNRkSkqQmQ0KS1GRISJKaDAlJUpMhIUlqMiQkSU2GhCSpqdeQSPJYkj1JPpFkoms7L8ndSR7upudO6b85yb4ke5Os7bM2SdKpzcWZxHdW1SurarxbvhHYWVWrgJ3dMkkuBzYALweuBm5JsmQO6pMkNczH5aZ1wNZufiuwfkr77VX1TFU9CuwDrpj78iRJx/QdEgV8IMmuJNd3bRdU1ZMA3fT8rn0F8MSUdfd3bcdJcn2SiSQThw4d6rF0SdJZPW//yqo6mOR84O4kD52kb6Zpq+c0VN0K3AowPj7+nM8lSbOn1zOJqjrYTZ8G/orB5aOnklwI0E2f7rrvB1ZOWf1i4GCf9UmSTq63kEjyoiRfdWweeD3wKWA7sLHrthG4q5vfDmxIcnaSy4BVwD191SdJOrU+LzddAPxVkmP7eU9VvT/JvcC2JNcBjwPXAFTV/Um2AQ8AzwI3VNXRHuuTJJ1CbyFRVY8A3zxN+78AVzXWuQm4qa+aJEkz4xPXkqQmQ0KS1GRISJKaDAlJUpMhIUlqMiQkSU2GhCSpyZCQJDUZEpKkJkNCktRkSEiSmgwJSVKTISFJajIkJElNhoQkqcmQkCQ1GRKSpCZDQpLUZEhIkpoMCUlSkyEhSWoyJCRJTYaEJKnJkJAkNRkSkqQmQ0KS1HTWfBcgSRrenbsPsGXHXg4enuSiZWNsWrua9WtW9LY/Q0KSzhB37j7A5jv2MHnkKAAHDk+y+Y49AL0FhZebJOkMsWXH3i8FxDGTR46yZcfe3vZpSEjSGeLg4ckZtc8GQ0KSzhAXLRubUftsMCQk6Qyxae1qxpYuOa5tbOkSNq1d3ds+HbiWpDPEscFp726SJE1r/ZoVvYbCibzcJElq6j0kkixJsjvJ+7rl85LcneThbnrulL6bk+xLsjfJ2r5rkySd3FycSbwZeHDK8o3AzqpaBezslklyObABeDlwNXBLkiVIkubNKUMiyQuT/GqSP+qWVyX5vmE2nuRi4D8B75jSvA7Y2s1vBdZPab+9qp6pqkeBfcAVQx2FJKkXw5xJvAt4BnhNt7wf+K0ht/97wC8BX5zSdkFVPQnQTc/v2lcAT0zpt79rO06S65NMJJk4dOjQkGVIkk7HMCHxsqp6K3AEoKomgZxqpe5s4+mq2jVkLdNts57TUHVrVY1X1fjy5cuH3LQk6XQMcwvsF5KM0f3BTvIyBmcWp3Il8P1Jvhc4B/jqJH8KPJXkwqp6MsmFwNNd//3AyinrXwwcHPI4JEk9GOZM4i3A+4GVSd7NYLD5l061UlVtrqqLq+pSBgPSH6yqHwO2Axu7bhuBu7r57cCGJGcnuQxYBdwzk4ORpDPJnbsPcOXNH+SyG/+aK2/+IHfuPjDfJT3HKc8kquruJPcBr2ZwSejNVfWZ57HPm4FtSa4DHgeu6fZzf5JtwAPAs8ANVXW0vRlJOnPNx2u/T0eqnnPZ/7mdklcAlzIlVKrqjv7KGs74+HhNTEzMdxmSNGNX3vxBDkzz9tYVy8b46I3f1eu+k+yqqvFh+p7yTCLJbcArgPv58l1KBcx7SEjSmWo+Xvt9OoYZuH51VV3eeyWSNEIuWjY27ZlEn6/9Ph3DDFx/rHsaWpI0S+bjtd+nY5gzia0MguKfGdz6GqCq6hW9ViZJi9h8vPb7dAwTErcBbwT2cPyT05Kk52GuX/t9OoYJiceranvvlUiSFpxhQuKhJO8B/jdTnrReCLfASpL6NUxIjDEIh9dPafMWWEkaAcM8cf2muShEkrTwDPMw3buY/m2sP9FLRZKkBWOYy03vmzJ/DvAD+HZWSRoJw1xu+supy0neC/xtbxVJkhaM0/mN61XAJbNdiCRp4RlmTOJzDMYk0k3/GfjlnuuSJC0Aw1xu+qq5KESStPA0QyLJt5xsxaq6b/bLkSQtJCc7k/idk3xWQL+/iiFJmnfNkKiq75zLQiRJC88wA9dLgZ8GXts1/R3w9qo60mNdkqQFYJiH6d4GLAVu6Zbf2LX9ZF9FSZIWhmFC4lVV9c1Tlj+Y5JN9FSRJWjiGeZjuaJKXHVtI8lLgaH8lSZIWimHOJDYBH0ryCIMH6l4C+GZYSRoBwzxMtzPJKmA1g5B4qKqeOcVqkqRF4JSXm7rxh18A/qOqPmlASNLoGGZM4vsZjEFsS3Jvkl9M4gv+JGkEnDIkqurTVfXWqvpW4EeAVwCP9l6ZJGneDTNwTZJLgR8CfpjBWcUv9ViTJGmBGOaJ648zeJhuG3BNVT3Se1WSpAVhmDOJjVX1UO+VSJIWnGHGJAwISRpRQ41JSNKouXP3Abbs2MvBw5NctGyMTWtXs37Nivkua84ZEpJ0gjt3H2DzHXuYPDJ4A9GBw5NsvmMPwMgFxTAP070wya8m+aNueVWS7+u/NEmaH1t27P1SQBwzeeQoW3bsnaeK5s8wD9O9C3gGeE23vB/4rd4qkqR5dvDw5IzaF7NhQuJlVfVW4AhAVU0yeIfTSSU5J8k9ST6Z5P4kv9G1n5fk7iQPd9Nzp6yzOcm+JHuTrD3NY5Kk5+WiZWMzal/MhgmJLyQZY/C71nSvDR/m/U3PAN/V/RbFK4Grk7wauBHYWVWrgJ3dMkkuBzYALweuBm5JsmRmhyNJz9+mtasZW3r8n5+xpUvYtHb1PFU0f4YJibcA7wdWJnk3gz/sp3ziugb+vVtc2v1TwDpga9e+FVjfza8Dbq+qZ6rqUWAfcMWQxyFJs2b9mhX89hu+iRXLxgiwYtkYv/2Gbxq5QWs4xd1NSb4COBd4A/BqBpeZ3lxVnxlm492ZwC7ga4E/qKqPJ7mgqp4EqKonk5zfdV8B/MOU1fd3bSdu83rgeoBLLvE9g5L6sX7NipEMhROd9Eyiqr4I/GxV/UtV/XVVvW/YgOjWP1pVrwQuBq5I8o0n6T7dOEdNs81bq2q8qsaXL18+bCmSpNMwzOWmu7vXg6/sBp3PS3LeTHZSVYeBv2Mw1vBUkgsBuunTXbf9wMopq10MHJzJfiRJs2uYkPgJ4AbgwwwuHe0CJk61UpLlSZZ182PA64CHgO3Axq7bRuCubn47sCHJ2UkuA1YB9wx9JJKkWTfMz5dedprbvhDY2o1LfAWwrarel+RjDH7A6DrgceCabj/3J9kGPAA8C9xQVUcb25YkzYFUPeey//EdkqXATwOv7Zr+Dnh7VR3pt7RTGx8fr4mJU57USJKmSLKrqsaH6TvMu5vexuD21Vu65Td2bT95euVJks4Uw4TEq7oH4o75YJJP9lWQJGnhGGbg+mj3lDUASV7K4CdMJUmL3DBnEpuADyV5hMGzDC8B3tRrVZKkBWGYu5t2JlkFrGYQEg9V1TDvbpIkneFOGRJJzgF+Bvh2Bk9A/32SP6yqz/ddnCRpfg1zuelPgM8B/7Nbvhb4X3TPN0iSFq9hQmL1CXc3fci7myRpNAxzd9Pu7ncgAEjybcBH+ytJkrRQDHMm8W3Ajyd5vFu+BHgwyR4GPxvxit6qkyTNq2FC4ureq5AkLUjD3AL76bkoRJK08AwzJiFJGlGGhCSpyZCQJDUZEpKkJkNCktQ0zC2wkqQh3Ln7AFt27OXg4UkuWjbGprWrWb9mxXyX9bwYEpI0C+7cfYDNd+xh8sjg53YOHJ5k8x17AM7ooPBykyTNgi079n4pII6ZPHKULTv2zlNFs8OQkKRZcPDw5IzazxSGhCTNgouWjc2o/UxhSEjSLNi0djVjS5cc1za2dAmb1q6ep4pmhwPXkjQLjg1Oe3eTJGla69esOOND4URebpIkNRkSkqQmQ0KS1GRISJKaDAlJUpMhIUlqMiQkSU2GhCSpyZCQJDUZEpKkpt5CIsnKJB9K8mCS+5O8uWs/L8ndSR7upudOWWdzkn1J9iZZ21dtkqTh9Hkm8SzwX6vqG4BXAzckuRy4EdhZVauAnd0y3WcbgJcDVwO3JFky7ZYlSXOit5Coqier6r5u/nPAg8AKYB2wteu2FVjfza8Dbq+qZ6rqUWAfcEVf9UmSTm1OxiSSXAqsAT4OXFBVT8IgSIDzu24rgCemrLa/aztxW9cnmUgycejQoV7rlqRR13tIJPlK4C+Bn6+qfztZ12na6jkNVbdW1XhVjS9fvny2ypQkTaPXkEiylEFAvLuq7uian0pyYff5hcDTXft+YOWU1S8GDvZZnyTp5Pq8uynAO4EHq+p3p3y0HdjYzW8E7prSviHJ2UkuA1YB9/RVnyTp1Pr8ZborgTcCe5J8omv7FeBmYFuS64DHgWsAqur+JNuABxjcGXVDVR3tsT5J0in0FhJV9RGmH2cAuKqxzk3ATX3VJEmaGZ+4liQ1GRKSpCZDQpLUZEhIkpoMCUlSkyEhSWoyJCRJTYaEJKnJkJAkNRkSkqQmQ0KS1GRISJKaDAlJUpMhIUlqMiQkSU2GhCSpyZCQJDUZEpKkJkNCktRkSEiSmgwJSVKTISFJajIkJElNhoQkqcmQkCQ1GRKSpCZDQpLUZEhIkpoMCUlSkyEhSWoyJCRJTYaEJKnJkJAkNRkSkqQmQ0KS1NRbSCS5LcnTST41pe28JHcnebibnjvls81J9iXZm2RtX3VJkobX55nEHwNXn9B2I7CzqlYBO7tlklwObABe3q1zS5IlPdYmSRpCbyFRVR8GPntC8zpgaze/FVg/pf32qnqmqh4F9gFX9FWbJGk4cz0mcUFVPQnQTc/v2lcAT0zpt79re44k1yeZSDJx6NChXouVpFG3UAauM01bTdexqm6tqvGqGl++fHnPZUnSaJvrkHgqyYUA3fTprn0/sHJKv4uBg3NcmyTpBHMdEtuBjd38RuCuKe0bkpyd5DJgFXDPHNcmSTrBWX1tOMl7ge8AXpxkP/AW4GZgW5LrgMeBawCq6v4k24AHgGeBG6rqaF+1SZKG01tIVNW1jY+uavS/Cbipr3okSTO3UAauJUkLkCEhSWoyJCRJTYaEJKnJkJAkNRkSkqQmQ0KS1GRISJKaDAlJUpMhIUlqMiQkSU2GhCSpyZCQJDUZEpKkJkNCktRkSEiSmgwJSVKTISFJajIkJElNhoQkqcmQkCQ1GRKSpCZDQpLUZEhIkpoMCUlSkyEhSWoyJCRJTYaEJKnJkJAkNRkSkqQmQ0KS1GRISJKaDAlJUpMhIUlqMiQkSU1nzXcBJ0pyNfD7wBLgHVV182zv487dB9iyYy8HD09y0bIxNq1dzfo1K2at/1ytM1d1SRpdCyokkiwB/gD4bmA/cG+S7VX1wGzt487dB9h8xx4mjxwF4MDhSTbfsQdg2j+WM+0/V+vMVV2SRttCu9x0BbCvqh6pqi8AtwPrZnMHW3bs/dIfyWMmjxxly469s9J/rtaZq7okjbaFFhIrgCemLO/v2r4kyfVJJpJMHDp0aMY7OHh4stf2uVpnruqSNNoWWkhkmrY6bqHq1qoar6rx5cuXz3gHFy0b67V9rtaZq7okjbaFFhL7gZVTli8GDs7mDjatXc3Y0iXHtY0tXcKmtatnpf9crTNXdUkabQtq4Bq4F1iV5DLgALAB+JHZ3MGxAdph7/CZaf+5Wmeu6pI02lJVp+41h5J8L/B7DG6Bva2qbmr1HR8fr4mJibkqTZIWhSS7qmp8mL4L7UyCqvob4G/muw5J0sIbk5AkLSCGhCSpyZCQJDUZEpKkJkNCktRkSEiSmgwJSVKTISFJajIkJElNC+61HDOR5BDw6eexiRcDn5mlcs40HvvoGuXjH+Vjhy8f/0uqaqjXaJ/RIfF8JZkY9v0li43HPprHDqN9/KN87HB6x+/lJklSkyEhSWoa9ZC4db4LmEce++ga5eMf5WOH0zj+kR6TkCSd3KifSUiSTsKQkCQ1jWRIJLk6yd4k+5LcON/1zLUkjyXZk+QTSRb1778muS3J00k+NaXtvCR3J3m4m547nzX2qXH8v57kQPf9f6L7yeBFJ8nKJB9K8mCS+5O8uWtf9N//SY59xt/9yI1JJFkC/BPw3cB+4F7g2qp6YF4Lm0NJHgPGq2rRP1SU5LXAvwN/UlXf2LW9FfhsVd3c/U/CuVX1y/NZZ18ax//rwL9X1f+Yz9r6luRC4MKqui/JVwG7gPXAf2aRf/8nOfYfYobf/SieSVwB7KuqR6rqC8DtwLp5rkk9qaoPA589oXkdsLWb38rgP55FqXH8I6Gqnqyq+7r5zwEPAisYge//JMc+Y6MYEiuAJ6Ys7+c0/+WdwQr4QJJdSa6f72LmwQVV9SQM/mMCzp/neubDzyb5x+5y1KK73HKiJJcCa4CPM2Lf/wnHDjP87kcxJDJN22hdc4Mrq+pbgO8BbuguSWh0vA14GfBK4Engd+a1mp4l+UrgL4Gfr6p/m+965tI0xz7j734UQ2I/sHLK8sXAwXmqZV5U1cFu+jTwVwwuwY2Sp7prtseu3T49z/XMqap6qqqOVtUXgT9iEX//SZYy+CP57qq6o2seie9/umM/ne9+FEPiXmBVksuSvADYAGyf55rmTJIXdQNZJHkR8HrgUydfa9HZDmzs5jcCd81jLXPu2B/Izg+wSL//JAHeCTxYVb875aNF//23jv10vvuRu7sJoLvt6/eAJcBtVXXT/FY0d5K8lMHZA8BZwHsW8/EneS/wHQxekfwU8BbgTmAbcAnwOHBNVS3Kwd3G8X8Hg8sNBTwG/NSxa/SLSZJvB/4e2AN8sWv+FQbX5hf193+SY7+WGX73IxkSkqThjOLlJknSkAwJSVKTISFJajIkJElNhoQkqcmQkHqQ5DeTvG6G6zyW5MV91SSdDm+BlWYgyZKqOtrTth9jRN7OqzOHZxIaKUkuTfJQkq3dS87+IskLu8+uSrK7+62N25Kc3bU/luTXknwEuCbJ65N8LMl9Sf68ez/Oifv54yQ/OGX93+j670ny9V371yT5QLfPtzPlvWJJfizJPd07/9+eZEmSV3U1n9M9OX9/km+ci39vGl2GhEbRauDWqnoF8G/AzyQ5B/hj4Ier6psYPI3+01PW+XxVfTvwt8B/A17XvSRxAviFIfb5ma7/24Bf7NreAnykqtYweFXEJQBJvgH4YQYvYnwlcBT40aq6t+v3W8BbgT+tqkX5Sg0tHGfNdwHSPHiiqj7azf8p8HPA3cCjVfVPXftW4AYGr28B+LNu+mrgcuCjg9fj8ALgY0Ps89jL5XYBb+jmX3tsvqr+Osm/du1XAd8K3NvtY4wvv4TuNxm8f+zzXd1SrwwJjaITB+KK6V8hP9V/dNMAd1fVtTPc5zPd9CjH/3c33aBggK1VtXmaz84DvhJYCpwzpS6pF15u0ii6JMlruvlrgY8ADwGXJvnarv2NwP+ZZt1/AK481i/JC5N83WnW8WHgR7vtfA9w7AdgdgI/mOT87rPzkryk++xW4FeBdwP//TT3Kw3NkNAoehDYmOQfGfyf+duq6vPAm4A/T3LszZl/eOKKVXWIwW8kv7db/x+Arz/NOn4DeG2S+xi8sv3xbh8PMBj3+EC3j7uBC5P8OPBsVb0HuBl4VZLvOs19S0PxFliNlO6nHN9XVd4VJA3BMwlJUpNnEpKkJs8kJElNhoQkqcmQkCQ1GRKSpCZDQpLU9P8BvVCjWZwsZ4IAAAAASUVORK5CYII=",
      "text/plain": [
       "<Figure size 432x432 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "import matplotlib.pyplot as plt\n",
    "fig, ax = plt.subplots(1, 1, figsize=[6, 6])\n",
    "\n",
    "ax.plot(pn.Ps, pn['pore.volume'], 'o')\n",
    "ax.set_ylabel('pore volume')\n",
    "ax.set_xlabel('pore index');"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Mixing Many Subdomains of Different Shape\n",
    "Because subdomains are now very abstract (actually just labels), it is possible to define multiple subdomains with different shape and apply models to each. So far we have added ``'pore.seed'`` and ``'pore.diameter'`` models to the ``'left'`` and ``'right'`` pores.  We can now freely add another set of models to the ``'front'`` and ``'back'``, even though they partially overlap:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 36,
   "metadata": {},
   "outputs": [],
   "source": [
    "Ps = pn.pores(['front', 'back'])\n",
    "Ts = pn.find_neighbor_throats(Ps, asmask=True)\n",
    "pn['throat.front'] = Ts\n",
    "pn['throat.back'] = ~Ts"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 37,
   "metadata": {},
   "outputs": [],
   "source": [
    "pn.add_model(propname='throat.diameter', \n",
    "             model=op.models.geometry.throat_size.from_neighbor_pores,\n",
    "             domain='front', \n",
    "             mode='min')\n",
    "pn.add_model(propname='throat.diameter', \n",
    "             model=op.models.geometry.throat_size.from_neighbor_pores,\n",
    "             domain='back',\n",
    "             mode='max')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Now we can see that the throat diameters have been added to the network:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 38,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "══════════════════════════════════════════════════════════════════════════════\n",
      "net : <openpnm.network.Cubic at 0x1f0626a3a40>\n",
      "――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――\n",
      "  #  Properties                                                   Valid Values\n",
      "――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――\n",
      "  1  pore.coords                                                       25 / 25\n",
      "  2  pore.diameter                                                     11 / 25\n",
      "  3  pore.new_array                                                     9 / 25\n",
      "  4  pore.seed                                                         25 / 25\n",
      "  5  pore.values                                                       25 / 25\n",
      "  6  pore.volume                                                       11 / 25\n",
      "  7  throat.conns                                                      40 / 40\n",
      "  8  throat.diameter                                                   40 / 40\n",
      "――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――\n",
      "  #  Labels                                                 Assigned Locations\n",
      "――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――\n",
      "  1  pore.all                                                               25\n",
      "  2  pore.back                                                               5\n",
      "  3  pore.front                                                              5\n",
      "  4  pore.left                                                               6\n",
      "  5  pore.right                                                              5\n",
      "  6  pore.surface                                                           16\n",
      "  7  throat.back                                                            22\n",
      "  8  throat.front                                                           18\n",
      "  9  throat.surface                                                         16\n",
      "――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――\n"
     ]
    }
   ],
   "source": [
    "print(pn)"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.10.13"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
